package me.jzn.alib.pref;

import android.os.Build;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.io.File;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;

import me.jzn.alib.utils.AndrPathUtil;
import me.jzn.core.cipher.AES128Coder;
import me.jzn.core.cipher.BaseCipherCoder.CipherMode;
import me.jzn.core.utils.ByteUtil;
import me.jzn.core.utils.HashUtil;
import me.jzn.core.utils.HashUtil.HashType;
import me.jzn.core.utils.StrUtil;

/**
 * Created by xqs on 2017/12/28.
 *
 * @see androidx.security.crypto.EncryptedSharedPreferences
 */
@Deprecated
public class CipherPref extends Pref {
    private AES128Coder mAes;
    private String mOrigName;
    private String mPrefix;

    /**
     * 慎用
     *
     * @param name
     */
    @Deprecated
    public CipherPref(String name) {
        super(hash(name));
        this.mOrigName = name;

        String hashkey = hash("this_##_key");
        String rawKey = super.get(hashkey);
        if (rawKey != null) {
            byte[] key = ByteUtil.fromHex(rawKey);
            mAes = new AES128Coder(key, CipherMode.CBC);
        } else {
            byte[] key = new byte[16];
            Random random = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? ThreadLocalRandom.current() : new Random();
            random.nextBytes(key);
            super.set(hashkey, ByteUtil.toHex(key));
            mAes = new AES128Coder(key, CipherMode.CBC);
        }
    }

    @Deprecated
    public CipherPref(@NonNull String name, @NonNull byte[] key) {
        super(hash(name));
        this.mPrefix = "";
        this.mOrigName = name;
        mAes = new AES128Coder(key, CipherMode.CBC);
    }

    public CipherPref(@Nullable String prefix, @NonNull String name, @NonNull byte[] key) {
        super(trimPrefix(prefix) + hash(name));
        this.mPrefix = trimPrefix(prefix);
        this.mOrigName = name;
        mAes = new AES128Coder(key, CipherMode.CBC);
    }

    public Map<String, ?> getAll() {
        throw new UnsupportedOperationException("加密后无法根据key获取value值，请根据放入的数据自行获取");
    }

    /**
     * 无需key
     *
     * @return
     */
    public static final File getFile(String prefix, String name) {
        return AndrPathUtil.getPrefFile(trimPrefix(prefix) + hash(name));
    }

    protected static final String hash(String src) {
        byte[] hash = HashUtil.hash(HashType.CRC32, StrUtil.bytesUtf8(src));
        return 'a' + ByteUtil.toHex(hash);
    }

    protected byte[] decode(String ciphered) {
        return mAes.decrypt(ByteUtil.fromHex(ciphered));
    }

    protected String encode(byte[] value) {
        return ByteUtil.toHex(mAes.encrypt(value));
    }

    //=============================================

    @Override
    public boolean contains(String key) {
        return super.contains(hash(key));
    }

    @Deprecated
    @Override
    public void rename(String newName) {
        super.rename(mPrefix + hash(newName));
        this.mOrigName = newName;
    }

    @Override
    public void set(String... kvs) {
        String[] arr = new String[kvs.length];
        for (int i = 0; i < kvs.length; i = i + 2) {
            arr[i] = hash(kvs[i]);
            arr[i + 1] = encode(StrUtil.bytesUtf8(kvs[i + 1]));
        }
        super.set(arr);
    }

    @Override
    public void set(String key, boolean value) {
        String newKey = hash(key);
        byte[] bytes = {(byte) (value ? 1 : 0)};
        String newValue = encode(bytes);
        super.set(newKey, newValue);
    }

    @Override
    public void set(String key, int value) {
        String newKey = hash(key);
        byte[] bytes = ByteUtil.from(value);
        String newValue = encode(bytes);
        super.set(newKey, newValue);
    }

    @Override
    public void set(String key, float value) {
        int newValue = Float.floatToIntBits(value);
        this.set(key, newValue);
    }

    @Override
    public void set(String key, long value) {
        String newKey = hash(key);
        byte[] bytes = ByteUtil.from(value);
        String newValue = encode(bytes);
        super.set(newKey, newValue);
    }

    @Override
    public void set(String key, Set<String> value) {
        if (value == null) {
            return;
        }
        String newKey = hash(key);
        Set<String> newValue = new HashSet<>(value.size());
        for (String str : value) {
            newValue.add(encode(StrUtil.bytesUtf8(str)));
        }
        super.set(newKey, newValue);
    }

    @Override
    public Set<String> getStringSet(String key) {
        return this.getStringSet(key, null);
    }

    @Override
    public Set<String> getStringSet(String key, Set<String> defValue) {
        Set<String> set = super.getStringSet(hash(key), null);
        if (set == null) {
            return defValue;
        }
        Set<String> newSet = new HashSet<>(set.size());
        for (String str : set) {
            newSet.add(StrUtil.newUtf8(decode(str)));
        }
        return newSet;
    }

    @Override
    public String get(String key) {
        return this.get(key, null);
    }

    @Override
    public String get(String key, String defValue) {
        String newValue = super.get(hash(key), null);
        return newValue == null ? defValue : StrUtil.newUtf8(decode(newValue));
    }

    @Override
    public Boolean getBoolean(String key) {
        return this.getBoolean(key, false);
    }

    @Override
    public boolean getBoolean(String key, boolean defValue) {
        String newValue = super.get(hash(key), null);
        if (newValue == null) {
            return defValue;
        }
        byte[] bytes = decode(newValue);
        return bytes[0] == 1;
    }

    @Override
    public int getInt(String key) {
        return this.getInt(key, -1);
    }

    @Override
    public int getInt(String key, int defValue) {
        String newValue = super.get(hash(key), null);
        if (newValue == null) {
            return defValue;
        }
        byte[] bytes = decode(newValue);
        BigInteger bigInt = ByteUtil.toBigInt(Arrays.copyOf(bytes, 4));
        return bigInt.intValue();
    }

    @Override
    public long getLong(String key) {
        return this.getLong(key, -1);
    }

    @Override
    public long getLong(String key, long defValue) {
        String newValue = super.get(hash(key), null);
        if (newValue == null) {
            return defValue;
        }
        byte[] bytes = decode(newValue);
        BigInteger bigInt = ByteUtil.toBigInt(Arrays.copyOf(bytes, 8));
        return bigInt.longValue();
    }

    @Override
    public float getFloat(String key) {
        return this.getFloat(key, -1F);
    }

    @Override
    public float getFloat(String key, float defValue) {
        String newValue = super.get(hash(key), null);
        if (newValue == null) {
            return defValue;
        }
        int bits = this.getInt(key);
        return Float.intBitsToFloat(bits);
    }

    @Override
    public void remove(String key) {
        super.remove(hash(key));
    }

    @Override
    public String getName() {
        return mOrigName;
    }

    public String getHashName() {
        return super.getName();
    }

    private static final String trimPrefix(String prefix) {
        return prefix == null ? "" : prefix;
    }
}
